Unstructured grids are a powerful tool to store Geoscience data. Unlike traditional, structured grids, unstructured grids have flexible geometries and variable resolution. This makes them incredibly useful for filling in irregularly shaped domains like Earth's oceans, or for achieving high resolutions in localized regions. However, working with unstructured datasets comes with additional challenges. The grids are made up of various shapes with varying sizes, so many datasets store additional information that describes their grid's geometry. Before we can plot our data, we must convert this connectivity information into a format compatible with plotting software. In this notebook, we will discuss and compare various ways in which we can visualize unstructured datasets.
# Recognition of unstructured grids and data handlingimportuxarrayasux# General Plottingimportcartopy.crsasccrs# Plotting with HoloVizimportholoviewsashvimporthvplot.pandasimportgeoviews.featureasgf
The global data sets used in this example are from the same experiment, but run at several resolutions from 30km to 3.75km. Due to their size, the higher resolution data sets are only distributed with two variables in them:
relhum_200hPa: Relative humidity vertically interpolated to 200 hPa
vorticity_200hPa: Relative vorticity vertically interpolated to 200 hPa
The relhum_200hPa is computed on the MPAS ‘primal’ mesh, while the vorticity_200hPa is computed on the MPAS ‘dual’ mesh. Note that data may also be sampled on the edges of the primal mesh. This example does not include/cover edge-centered data.
We will first load the data and grid information through uxarray.open_dataset, which lets us load files directly from the internet without downloading them.
In [2]:
# # Load data files from glade# file_dir_30km = "/glade/campaign/cisl/vast/clyne/old_glade_p/FalkoJudt/dyamond_1/30km/"# file_dir_15km = "/glade/campaign/cisl/vast/clyne/old_glade_p/FalkoJudt/dyamond_1/15km/"# file_dir_7_5km = "/glade/campaign/cisl/vast/clyne/old_glade_p/FalkoJudt/dyamond_1/7.5km/"# file_dir_3_75km = "/glade/campaign/cisl/vast/clyne/old_glade_p/FalkoJudt/dyamond_1/3.75km/"# Use the local copies of the above glade filesfile_dir_30km="data/30km/"file_dir_15km="data/15km/"file_dir_7_5km="data/7.5km/"file_dir_3_75km="data/3.75km/"# Note: The grid in the 3.75km dir does not have grid definition variables such as "verticesOnEdge".# Since UXarray assumed those varables as required for MPAS-recognition, it can't open 3.75km for now.# This will be fixed on UXarray soon.grid_file_30km="x1.655362.grid.nc"grid_file_15km="x1.2621442.grid.nc"grid_file_7_5km="x1.10485762.grid.nc"# 7.5kmgrid_file_3_75km="x1.41943042.grid.nc"# 3.75kmdata_filename="diag.2016-08-20_00.00.00.nc"# Open datasets from filesds_30km=ux.open_dataset(file_dir_30km+grid_file_30km,file_dir_30km+data_filename)ds_15km=ux.open_dataset(file_dir_15km+grid_file_15km,file_dir_15km+data_filename)# ds_7_5km = ux.open_dataset(file_dir_7_5km + grid_file_7_5km, file_dir_7_5km + data_filename)# ds_3_75km = ux.open_dataset(file_dir_3_75km + grid_file_3_75km, file_dir_3_75km + data_filename)
Below we can see some information about the grid structure of each dataset. These are MPAS datasets, which means they contain a Primal mesh, composed of Voronoi regions, and a dual mesh, composed of Delaunay Triangles.
nMesh2_face and nMesh2_nodedescribe the number of faces and nodes the dataset has, repsectively, which vary with the resolution.
array([[b'2', b'0', b'1', b'6', b'-', b'0', b'8', b'-', b'2', b'0', b'_',
b'0', b'0', b':', b'0', b'0', b':', b'0', b'0', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ']],
dtype='|S1')
array([[b'2', b'0', b'1', b'6', b'-', b'0', b'8', b'-', b'2', b'0', b'_',
b'0', b'0', b':', b'0', b'0', b':', b'0', b'0', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ']],
dtype='|S1')
array([[b'2', b'0', b'1', b'6', b'-', b'0', b'8', b'-', b'2', b'0', b'_',
b'0', b'0', b':', b'0', b'0', b':', b'0', b'0', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ']],
dtype='|S1')
array([[b'2', b'0', b'1', b'6', b'-', b'0', b'8', b'-', b'2', b'0', b'_',
b'0', b'0', b':', b'0', b'0', b':', b'0', b'0', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ',
b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ']],
dtype='|S1')
HoloViz is a set of tools that simplifies Python visualizations by calling plotting libraries such as Datashader in the backend. The next section of the notebook discusses visualizations of unstructured grids using HoloViz tools.
Creating a vector image can be computationally expensive and will likely take quite some time, especially for larger datasets. Another approach can be to rasterize the data, which converts geometries (points etc.) to a raster image.
We will only look into the rasterization approaches for high-performance, km-scale data visualizations in this notebook
Primal mesh variable (Relative humidity) - No projection¶
In [10]:
%%timeraster=ds_15km[primal_var_name].sel(Time=0).plot.rasterize()# This is actually equivalent to calling as follows since we handle default values for several optional largs# raster = ds_15km[primal_var_name].sel(Time=0).plot.rasterize(colorbar=True,# cmap='coolwarm',# width=1000,# height=500,# tools=['hover'],# projection=None,# aggregator='mean',# interpolation='linear',# precompute=True,# dynamic=False,# npartitions=1)raster.opts(title="relhum_200hPa")
CPU times: user 443 ms, sys: 62 ms, total: 505 ms
Wall time: 522 ms